/**
 * GarinDriver -- Eclipse Public License - v 1.0
 * http://www.eclipse.org/legal/epl-v10.html
 */
package garin.parse.sql;

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
import garin.parse.CommentToken;
import garin.parse.StringToken;
import garin.parse.Token;
import garin.parse.WhitespaceToken;
import garin.parse.io.SetValueObserver;
import garin.parse.io.Source;
import garin.parse.io.StringSource;
import garin.parse.sql.sql2003.ControlToken;
import garin.parse.sql.sql2003.SqlStatement;
import garin.parse.sql.sql2003.StatementEndToken;
import garin.parse.sql.sql2003.StatementOperator;
import garin.parse.sql.sql2003.StatementWord;

import java.io.IOException;
import java.lang.reflect.Constructor;

/**
 * Builds a string to represent a SQL statement with color coding and formatting
 * based on token representations.
 * 
 * @author Mark Farnsworth
 */
public class SqlRenderStringBuilder implements SetValueObserver {
	private static Constructor<? extends SqlStatement> getConstructor(
			final String dialect) throws ClassNotFoundException,
			NoSuchMethodException {
		return statementClass(dialect).getConstructor(Source.class);
	}
	
	@SuppressWarnings("unchecked")
	private static Class<? extends SqlStatement> statementClass(
			final String dialect) throws ClassNotFoundException {
		final String clzName = "garin.parse.sql." + dialect + ".SqlStatement";
		return (Class<? extends SqlStatement>) Class.forName(clzName);
	}
	
	private final String string;
	private final StringBuilder writer = new StringBuilder();
	private Token token;
	private int parenActive;
	private boolean suppressBr;
	private long lastPos;
	
	public SqlRenderStringBuilder(final String text, final String dialect)
			throws Exception {
		final StringSource source = new StringSource(text);
		source.setValueObserver(this);
		final SqlStatement stmt = getConstructor(dialect).newInstance(source);
		stmt.init();
		source.read(stmt);
		string = writer.toString();
	}
	
	private void afterOutput() {
		final String value = token.value();
		if (token.matchClass(ControlToken.class) && value.equals("(")
				&& ++parenActive == 1) {
			final String html = "<br/><a href='javascript:showBlock()' class='whitespace' title='syntheticFormattingToken'> </a>";
			writer.append(html);
		} else if (token.matchClass(StatementOperator.class)
				&& value.equals(","))
			writer.append("<br/>");
		else if (token.matchClass(CommentToken.class))
			writer.append("<br/>");
		else if (token.matchClass(StatementEndToken.class))
			writer.append("<br/>");
		else if (token.matchClass(StatementWord.class)
				&& value.equalsIgnoreCase("SELECT")) writer.append("<br/>");
	}
	
	private void beforeOutput() {
		String value = token.value();
		if (null == value) return;
		value = value.toUpperCase();
		if (token.matchClass(ControlToken.class) && value.equals(")")
				&& parenActive-- == 1)
			writer.append("<br/>");
		else if (token.matchClass(CommentToken.class))
			writer.append("<br/>");
		else if (token.matchClass(StatementWord.class))
			if (token.matchClass(StatementWord.class)
					&& !(value.equals("LIKE") || value.equals("INTO")
							|| value.equals("TABLE") || value.equals("VALUES"))) {
				if (!suppressBr) writer.append("<br/>");
				suppressBr = value.equals("DELETE");
			}
	}
	
	private void doOutput() {
		String contents = token.value();
		if (token instanceof StatementWord) contents = contents.toUpperCase();
		contents = escapeHtml(contents);
		writer.append(contents);
	}
	
	private void endOutput() {
		writer.append("</a>");
	}
	
	private void format(final String cssClass) throws IOException {
		String s = "<a href='javascript:showBlock()' title='%s' class='%s'>";
		s = String.format(s, token.toString(), cssClass);
		writer.append(s);
	}
	
	public String get() {
		return string;
	}
	
	public void onSetValue(final Token token) throws Exception {
		SqlRenderStringBuilder.this.token = token;
		output();
	}
	
	private void output() throws IOException {
		if (token.start() < lastPos) return;
		beforeOutput();
		startOutput();
		doOutput();
		endOutput();
		afterOutput();
		lastPos = token.end();
	}
	
	private void startOutput() throws IOException {
		if (token.matchClass(CommentToken.class))
			format("comment");
		else if (token.matchClass(StatementOperator.class))
			format("statement");
		else if (token.matchClass(StatementEndToken.class))
			format("statement");
		else if (token.matchClass(StringToken.class))
			format("string");
		else if (token.matchClass(WhitespaceToken.class))
			format("whitespace");
		else
			format("normal");
	}
}
